﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace DDD.Core {
    /// <summary>
    /// メッシュ作成のためのヘルパークラス
    /// </summary>
    /// <remarks>
    /// 指定の形状を持つ<see cref="Mesh"/>オブジェクトを作成します。
    /// このクラスはお手軽に指定の形を作るためのお助けクラスです。
    /// </remarks>
    public class MeshBuilder {
        /// <summary>
        /// 球状のメッシュを作成します
        /// </summary>
        /// <remarks>
        /// メッシュには座標と法線､インデックスが含まれます｡アピアランスはnullのままです｡
        /// 法線の名前にnullを指定するか省略すると法線情報は含まれません｡
        /// </remarks>
        /// <param name="R">半径</param>
        /// <param name="N">水平方向の分割数(>=3)</param>
        /// <param name="M">垂直方向の分割数(>=2)</param>
        /// <param name="positionName">座標位置がバインドされるAttribute変数名</param>
        /// <param name="normalName">法線がバインドされるAttribute変数名(省略可)</param>
        /// <returns></returns>
        public static Mesh CreateSphere (float R, int M, int N, string positionName, string normalName) {
            if (R <= 0) {
                throw new ArgumentException ("Radius is invalid");
            }
            if (N < 3) {
                throw new ArgumentException ("Horizontal division number is invalid");
            }
            if (M < 2) {
                throw new ArgumentException ("Vertical division number is invalid");
            }
            if (positionName == null || positionName == "") {
                throw new ArgumentException ("Position name is null or empty");
            }
            if (normalName == "") {
                throw new ArgumentException ("Normal name is empty");
            }

            var PI = (float)Math.PI;


            // ******************************
            // 頂点データ
            // ******************************
            var vertices = new float[M + 1][][];
            var normals = new float[M + 1][][];

            // Ring 0
            vertices[0] = new float[1][] { new float[] { 0, R * 2, 0 } };
            normals[0] = new float[1][] { new float[] { 0, 1, 0 } };

            // Ring 1,M-1
            for (var i = 1; i < M; i++) {
                vertices[i] = new float[N][];
                normals[i] = new float[N][];

                for (var j = 0; j < N; j++) {
                    var a = i / (float)M * PI;
                    var b = j / (float)N * 2 * PI;

                    var x = R * (float)Math.Sin (a) * (float)Math.Cos (b);
                    var y = R + R * (float)Math.Cos (a);
                    var z = -R * (float)Math.Sin (a) * (float)Math.Sin (b);

                    var nx = (float)Math.Sin (a) * (float)Math.Cos (b);
                    var ny = (float)Math.Cos (a);
                    var nz = -(float)Math.Sin (a) * (float)Math.Sin (b);

                    vertices[i][j] = new float[] { x, y, z };
                    normals[i][j] = new float[] { nx, ny, nz };
                }
            }

            // Ring M
            vertices[M] = new float[1][] { new float[] { 0, 0, 0 } };
            normals[M] = new float[1][] { new float[] { 0, -1, 0 } };


            // ******************************
            // 頂点インデックス
            // ******************************
            var indices = new int[M + 1][][];

            // Ring 0
            indices[0] = new int[0][];

            // Ring 1
            indices[1] = new int[N][];
            for (var j = 0; j < N; j++) {
                var offset = 1;
                if (j < N - 1) {
                    indices[1][j] = new int[] { 0, offset + j, offset + j + 1 };
                }
                else {
                    indices[1][j] = new int[] { 0, offset + j, offset };
                }
            }

            // Ring 2,M-1
            for (var i = 2; i < M; i++) {
                indices[i] = new int[2 * N][];
                for (var j = 0; j < N; j++) {
                    var offset = (i - 1) * N + 1;
                    if (j < N - 1) {
                        indices[i][2 * j + 0] = new int[] { offset + j - N, offset + j, offset + j + 1 };
                        indices[i][2 * j + 1] = new int[] { offset + j - N, offset + j + 1, offset + j + 1 - N };
                    }
                    else {
                        indices[i][2 * j + 0] = new int[] { offset + j - N, offset + j, offset };
                        indices[i][2 * j + 1] = new int[] { offset + j - N, offset, offset - N };
                    }
                }
            }

            // Ring M
            indices[M] = new int[N][];
            for (var j = 0; j < N; j++) {
                var offset = (M - 1) * N + 1;
                if (j < N - 1) {
                    indices[M][j] = new int[] { offset + j - N, offset, offset + j - N + 1 };
                }
                else {
                    indices[M][j] = new int[] { offset + j - N, offset, offset - N };
                }
            }




            // ******************************
            // メッシュ作成
            // ******************************

            var positionArray = new VertexArray<float> ((M - 1) * N + 2, 3);
            var normalArray = new VertexArray<float> ((M - 1) * N + 2, 3);
            var v = 0;
            for (var i = 0; i < vertices.Length; i++) {
                for (var j = 0; j < vertices[i].Length; j++) {
                    positionArray.Set (v, 1, vertices[i][j]);
                    normalArray.Set (v, 1, normals[i][j]);
                    v++;
                }
            }

            var vbuf = new VertexBuffer ();
            vbuf.AddVertexArray (positionName, positionArray);
            if (normalName != null) {
                vbuf.AddVertexArray (normalName, normalArray);
            }

            var ibuf = new IndexBuffer<int> (PrimitiveType.Triangle, (M - 1) * N * 2);
            var p = 0;
            for (var i = 0; i < indices.Length; i++) {
                for (var j = 0; j < indices[i].Length; j++) {
                    ibuf.Set (p, 1, indices[i][j]);
                    p++;
                }
            }

            var mesh = new Mesh ();
            mesh.SetVertexBuffer (vbuf);
            mesh.SetIndexBuffer (ibuf);
            return mesh;
        }

        /// <summary>
        /// 立方状のメッシュを作成します
        /// </summary>
        /// <remarks>
        /// (メモ) M=Nに統一しようか？　個別に指定できても意味はないだろう。
        /// </remarks>
        /// <param name="L">1辺の長さ</param>
        /// <param name="M">垂直方向の分割数</param>
        /// <param name="N">水平方向の分割数</param>
        /// <param name="positionName">位置座標の名前</param>
        /// <param name="normalName">法線の名前(省略化)</param>
        /// <param name="texCoordsName">テクスチャー座標の名前(省略化)</param>
        /// <returns></returns>
        public static Mesh CreateBox (float L, int M, int N, string positionName, string normalName, string texCoordsName) {
            if (L <= 0) {
                throw new ArgumentException ("Length is invalid");
            }
            if (M <= 0) {
                throw new ArgumentException ("Vertical division number is invalid");
            }
            if (N <= 0) {
                throw new ArgumentException ("Horizontal division number is invalid");
            }
            if (positionName == null || positionName == "") {
                throw new ArgumentException ("Position name is null or empty");
            }
            if (normalName == "") {
                throw new ArgumentException ("Normal name is empty");
            }
            if (texCoordsName == "") {
                throw new ArgumentException ("TexCoords name is empty");
            }


            /*************************************
             * 頂点データ
             * **********************************/

            var positions = new float[6][][];
            var normals = new float[6][][];
            var texCoords = new float[6][][];

            // +X
            positions[0] = new float[(M + 1) * (N + 1)][];
            normals[0] = new float[(M + 1) * (N + 1)][];
            texCoords[0] = new float[(M + 1) * (N + 1)][];
            for (var z = 0; z <= N; z++) {
                for (var y = 0; y <= M; y++) {
                    positions[0][z * (M + 1) + y] = new float[3] { L / 2.0f, L * (1 - y / (float)M), L * (0.5f - z / (float)N) };
                    normals[0][z * (M + 1) + y] = new float[3] { 1, 0, 0 };
                    texCoords[0][z * (M + 1) + y] = new float[2] { y / (float)M, z / (float)N };
                }
            }

            // -X
            positions[1] = new float[(M + 1) * (N + 1)][];
            normals[1] = new float[(M + 1) * (N + 1)][];
            texCoords[1] = new float[(M + 1) * (N + 1)][];
            for (var z = 0; z <= N; z++) {
                for (var y = 0; y <= M; y++) {
                    positions[1][z * (M + 1) + y] = new float[3] { -L / 2.0f, L * (1 - y / (float)M), L * (-0.5f + z / (float)N) };
                    normals[1][z * (M + 1) + y] = new float[3] { -1, 0, 0 };
                    texCoords[1][z * (M + 1) + y] = new float[2] { y / (float)M, z / (float)N };
                }
            }

            // +Y
            positions[2] = new float[(N + 1) * (N + 1)][];
            normals[2] = new float[(N + 1) * (N + 1)][];
            texCoords[2] = new float[(N + 1) * (N + 1)][];
            for (var x = 0; x <= N; x++) {
                for (var z = 0; z <= N; z++) {
                    positions[2][x * (N + 1) + z] = new float[3] { L * (-0.5f + x / (float)N), L, L * (-0.5f + z / (float)N) };
                    normals[2][x * (N + 1) + z] = new float[3] { 0, 1, 0 };
                    texCoords[2][x * (N + 1) + z] = new float[2] { z / (float)M, x / (float)N };
                }
            }

            // -Y
            positions[3] = new float[(N + 1) * (N + 1)][];
            normals[3] = new float[(N + 1) * (N + 1)][];
            texCoords[3] = new float[(N + 1) * (N + 1)][];
            for (var x = 0; x <= N; x++) {
                for (var z = 0; z <= N; z++) {
                    positions[3][x * (N + 1) + z] = new float[3] { L * (-0.5f + x / (float)N), 0, L * (0.5f - z / (float)N) };
                    normals[3][x * (N + 1) + z] = new float[3] { 0, -1, 0 };
                    texCoords[3][x * (N + 1) + z] = new float[2] { z / (float)M, x / (float)N };
                }
            }

            // +Z
            positions[4] = new float[(M + 1) * (N + 1)][];
            normals[4] = new float[(M + 1) * (N + 1)][];
            texCoords[4] = new float[(M + 1) * (N + 1)][];
            for (var x = 0; x <= N; x++) {
                for (var y = 0; y <= M; y++) {
                    positions[4][x * (M + 1) + y] = new float[3] { L * (-0.5f + x / (float)N), L * (1 - y / (float)M), L / 2.0f };
                    normals[4][x * (M + 1) + y] = new float[3] { 0, 0, 1 };
                    texCoords[4][x * (M + 1) + y] = new float[2] { y / (float)M, x / (float)N };
                }
            }

            // -Z
            positions[5] = new float[(M + 1) * (N + 1)][];
            normals[5] = new float[(M + 1) * (N + 1)][];
            texCoords[5] = new float[(M + 1) * (N + 1)][];
            for (var x = 0; x <= N; x++) {
                for (var y = 0; y <= M; y++) {
                    positions[5][x * (M + 1) + y] = new float[3] { L * (0.5f - x / (float)N), L * (1 - y / (float)M), -L / 2.0f };
                    normals[5][x * (M + 1) + y] = new float[3] { 0, 0, -1 };
                    texCoords[5][x * (M + 1) + y] = new float[2] { y / (float)M, x / (float)N };
                }
            }

            /*************************************
             * インデックス
             ************************************/
            var indices = new int[6][][];

            // +X
            indices[0] = new int[M * 2 * N][];
            for (var z = 0; z < N; z++) {
                for (var y = 0; y < M; y++) {
                    var i = z * (M + 1) + y;
                    indices[0][(z * M + y) * 2 + 0] = new int[] { i, i + 1, i + (M + 1) };
                    indices[0][(z * M + y) * 2 + 1] = new int[] { i + (M + 1), i + 1, i + (M + 1) + 1 };
                }
            }

            // -X
            indices[1] = new int[M * 2 * N][];
            for (var z = 0; z < N; z++) {
                for (var y = 0; y < M; y++) {
                    var i = z * (M + 1) + y;
                    indices[1][(z * M + y) * 2 + 0] = new int[] { i, i + 1, i + (M + 1) };
                    indices[1][(z * M + y) * 2 + 1] = new int[] { i + (M + 1), i + 1, i + (M + 1) + 1 };
                }
            }

            // +Y
            indices[2] = new int[N * 2 * N][];
            for (var x = 0; x < N; x++) {
                for (var z = 0; z < N; z++) {
                    var i = x * (N + 1) + z;
                    indices[2][(x * N + z) * 2 + 0] = new int[] { i, i + 1, i + (N + 1) };
                    indices[2][(x * N + z) * 2 + 1] = new int[] { i + (N + 1), i + 1, i + (N + 1) + 1 };
                }
            }

            // -Y
            indices[3] = new int[N * 2 * N][];
            for (var x = 0; x < N; x++) {
                for (var z = 0; z < N; z++) {
                    var i = x * (N + 1) + z;
                    indices[3][(x * N + z) * 2 + 0] = new int[] { i, i + 1, i + (N + 1) };
                    indices[3][(x * N + z) * 2 + 1] = new int[] { i + (N + 1), i + 1, i + (N + 1) + 1 };
                }
            }

            // +Z
            indices[4] = new int[M * 2 * N][];
            for (var x = 0; x < N; x++) {
                for (var y = 0; y < M; y++) {
                    var i = x * (M + 1) + y;
                    indices[4][(x * M + y) * 2 + 0] = new int[] { i, i + 1, i + (M + 1) };
                    indices[4][(x * M + y) * 2 + 1] = new int[] { i + (M + 1), i + 1, i + (M + 1) + 1 };
                }
            }

            // -Z
            indices[5] = new int[M * 2 * N][];
            for (var x = 0; x < N; x++) {
                for (var y = 0; y < M; y++) {
                    var i = x * (M + 1) + y;
                    indices[5][(x * M + y) * 2 + 0] = new int[] { i, i + 1, i + (M + 1) };
                    indices[5][(x * M + y) * 2 + 1] = new int[] { i + (M + 1), i + 1, i + (M + 1) + 1 };
                }
            }



            /********************************************
             * メッシュの作成
             * *****************************************/

            var vertexCount = new int[6]{(M+1)*(N+1),  // +X
                                         (M+1)*(N+1),  // -X
                                         (N+1)*(N+1),  // +Y
                                         (N+1)*(N+1),  // -Y
                                         (M+1)*(N+1),  // +Z
                                         (M+1)*(N+1)}; // -Z
            var indexCount = new int[6]{M*2*N*3,  // +X
                                        M*2*N*3,  // -X
                                        N*2*N*3,  // +Y
                                        N*2*N*3,  // -Y
                                        M*2*N*3,  // +Z
                                        M*2*N*3};  // -Z

            var msh = new Mesh (6);
            for (var f = 0; f < 6; f++) {
                var posArray = new VertexArray<float> (vertexCount[f], 3);
                var normArray = new VertexArray<float> (vertexCount[f], 3);
                var texArray = new VertexArray<float> (vertexCount[f], 2);

                var vbuf = new VertexBuffer ();
                for (var v = 0; v < positions[f].Length; v++) {
                    posArray.Set (v, 1, positions[f][v]);
                    normArray.Set (v, 1, normals[f][v]);
                    texArray.Set (v, 1, texCoords[f][v]);
                }
                vbuf.AddVertexArray (positionName, posArray);
                if (normalName != null) {
                    vbuf.AddVertexArray (normalName, normArray);
                }
                if (texCoordsName != null) {
                    vbuf.AddVertexArray (texCoordsName, texArray);
                }

                var ibuf = new IndexBuffer<int> (PrimitiveType.Triangle, indexCount[f]);
                for (var p = 0; p < indices[f].Length; p++) {
                    ibuf.Set (p, 1, indices[f][p]);
                }

                msh.SetVertexBuffer (f, vbuf);
                msh.SetIndexBuffer (f, ibuf);
            }

            return msh;
        }

        /// <summary>
        /// ４角形のメッシュを作成します
        /// </summary>
        /// <remarks>
        /// (メモ) M=Nに統一しようか？　個別に指定できても意味はないだろう。
        /// </remarks>
        /// <param name="L">１辺の長さ</param>
        /// <param name="M">垂直方向の分割数</param>
        /// <param name="N">水平方向の分割数</param>
        /// <param name="positionName">位置座標の名前</param>
        /// <param name="normalName">法線の名前(省略化)</param>
        /// <param name="texCoordsName">テクスチャー座標の名前(省略可)</param>
        /// <returns></returns>
        public static Mesh CreateSquare (float L, int M, int N, string positionName, string normalName, string texCoordsName) {
            if (L <= 0) {
                throw new ArgumentException ("Length is invalid");
            }
            if (M <= 0) {
                throw new ArgumentException ("Vertical division number is invalid");
            }
            if (N <= 0) {
                throw new ArgumentException ("Horizontal division number is invalid");
            }
            if (positionName == null || positionName == "") {
                throw new ArgumentException ("Position name is null or empty");
            }
            if (normalName == "") {
                throw new ArgumentException ("Normal name is empty");
            }
            if (texCoordsName == "") {
                throw new ArgumentException ("TexCoords name is empty");
            }

            // **************************************
            // 頂点データ
            // **************************************

            var positions = new float[(M + 1) * (N + 1)][];
            var normals = new float[(M + 1) * (N + 1)][];
            var texCoords = new float[(M + 1) * (N + 1)][];

            for (var m = 0; m < M + 1; m++) {
                for (var n = 0; n < N + 1; n++) {
                    positions[m * (N + 1) + n] = new float[3] { -L / 2.0f + L / (float)N * n, L - L / (float)M * m, 0 };
                    normals[m * (N + 1) + n] = new float[3] { 0, 0, 1 };
                    texCoords[m * (N + 1) + n] = new float[2] { n / (float)N, 1 - m / (float)M };
                }
            }

            // **************************************
            // インデックス
            // **************************************
            var indices = new int[M * N * 2][];
            for (var m = 0; m < M; m++) {
                for (var n = 0; n < N; n++) {
                    var i = m * (N + 1) + n;
                    indices[m * N * 2 + n * 2 + 0] = new int[3] { i, i + (N + 1), i + (N + 2) };
                    indices[m * N * 2 + n * 2 + 1] = new int[3] { i, i + (N + 2), i + 1 };
                }
            }


            // **************************************
            // メッシュ
            // **************************************

            var posArray = new VertexArray<float> ((M + 1) * (N + 1), 3);
            var normArray = new VertexArray<float> ((M + 1) * (N + 1), 3);
            var texArray = new VertexArray<float> ((M + 1) * (N + 1), 2);

            var v = 0;
            for (var m = 0; m < M + 1; m++) {
                for (var n = 0; n < N + 1; n++) {
                    posArray.Set (v, 1, positions[v]);
                    normArray.Set (v, 1, normals[v]);
                    texArray.Set (v, 1, texCoords[v]);
                    v++;
                }
            }

            var vbuf = new VertexBuffer ();

            vbuf.AddVertexArray (positionName, posArray);
            if (normalName != null) {
                vbuf.AddVertexArray (normalName, normArray);
            }
            if (texCoordsName != null) {
                vbuf.AddVertexArray (texCoordsName, texArray);
            }

            var ibuf = new IndexBuffer<int> (PrimitiveType.Triangle, M * N * 2);

            var p = 0;
            for (var m = 0; m < M; m++) {
                for (var n = 0; n < N; n++) {
                    ibuf.Set (p, 1, indices[p]);
                    p++;
                    ibuf.Set (p, 1, indices[p]);
                    p++;
                }
            }

            var msh = new Mesh ();
            msh.SetVertexBuffer (vbuf);
            msh.SetIndexBuffer (ibuf);

            return msh;
        }

       
        /// <summary>
        /// 円柱状のメッシュを作成します
        /// </summary>
        /// <param name="R">半径</param>
        /// <param name="L">１辺の長さ</param>
        /// <param name="M">垂直方向の分割数</param>
        /// <param name="N">水平方向の分割数</param>
        /// <param name="positionName">位置座標の名前</param>
        /// <param name="normalName">法線の名前(省略化)</param>
        /// <returns></returns>
        public static Mesh CreateCylinder (float R, float L, int M, int N, string positionName, string normalName) {
            if (R <= 0) {
                throw new ArgumentException ("Radius is invalid");
            }
            if (L <= 0) {
                throw new ArgumentException ("Length is invalid");
            }
            if (M <= 0) {
                throw new ArgumentException ("Vertical division number is invalid");
            }
            if (N <= 0) {
                throw new ArgumentException ("Horizontal division number is invalid");
            }
            if (positionName == null || positionName == "") {
                throw new ArgumentException ("Position name is null or empty");
            }
            if (normalName == "") {
                throw new ArgumentException ("Normal name is empty");
            }

            var PI = (float)Math.PI;
            var vertexCount = (N + 1) * 2 + (M + 1) * N;
            var primitiveCount = N * 2 + M * N * 2;

            var positions = new float[vertexCount][];
            var normals = new float[vertexCount][];
            var primitives = new int[primitiveCount][];

            // -------------------------------------------
            // 頂点データ
            // -------------------------------------------
            var v = 0;

            // 上蓋
            positions[0] = new float[] { 0, L, 0 };
            normals[0] = new float[] { 0, 1, 0 };
            v += 1;
            for (var n = 0; n < N; n++) {
                positions[v] = new float[] { R * (float)Math.Cos (n / (float)N * 2 * PI), L, -R * (float)Math.Sin (n / (float)N * 2 * PI) };
                normals[v] = new float[] { 0, 1, 0 };
                v += 1;
            }

            // 側面
            for (var m = 0; m < M + 1; m++) {
                for (var n = 0; n < N; n++) {
                    positions[v] = new float[] { R * (float)Math.Cos (n / (float)N * 2 * PI), L * (1 - m / (float)M), -R * (float)Math.Sin (n / (float)N * 2 * PI) };
                    normals[v] = new float[] { (float)Math.Cos (n / (float)N * 2 * PI), 0, -(float)Math.Sin (n / (float)N * 2 * PI) };
                    v += 1;
                }
            }

            // 下蓋
            positions[v] = new float[] { 0, 0, 0 };
            normals[v] = new float[] { 0, -1, 0 };
            v += 1;
            for (var n = 0; n < N; n++) {
                positions[v] = new float[] { R * (float)Math.Cos (n / (float)N * 2 * PI), 0, -R * (float)Math.Sin (n / (float)N * 2 * PI) };
                normals[v] = new float[] { 0, -1, 0 };
                v += 1;
            }

            // -------------------------------------------
            // 頂点インデックス
            // -------------------------------------------
            var p = 0;

            // 上蓋
            for (var n = 0; n < N; n++) {
                if (n < N - 1) {
                    primitives[p++] = new int[] { 0, n + 1, n + 2 };
                }
                else {
                    primitives[p++] = new int[] { 0, n + 1, (n + 2) - N };
                }
            }

            // 側面
            for (var m = 0; m < M; m++) {
                for (var n = 0; n < N; n++) {
                    v = (N + 1) + m * N + n;
                    if (n < N - 1) {
                        primitives[p++] = new int[] { v, v + N, v + N + 1 };
                        primitives[p++] = new int[] { v, v + N + 1, v + 1 };
                    }
                    else {
                        primitives[p++] = new int[] { v, v + N, (v + N + 1) - N };
                        primitives[p++] = new int[] { v, (v + N + 1) - N, (v + 1) - N };
                    }
                }
            }

            // 下蓋
            var v0 = (N + 1) + (M + 1) * N;
            for (var n = 0; n < N; n++) {
                v = (N + 1) + (M + 1) * N + n + 1;
                if (n < N - 1) {
                    primitives[p++] = new int[] { v0, v + 1, v };
                }
                else {
                    primitives[p++] = new int[] { v0, v + 1 - N, v };
                }
            }


            // -------------------------------------------
            // メッシュ作成
            // -------------------------------------------
            var vbuf = new VertexBuffer ();
            var ibuf = new IndexBuffer<int> (PrimitiveType.Triangle, primitiveCount);

            var posArray = new VertexArray<float> (vertexCount, 3);
            var normArray = new VertexArray<float> (vertexCount, 3);

            for (v = 0; v < positions.Length; v++) {
                posArray.Set (v, 1, positions[v]);
                normArray.Set (v, 1, normals[v]);
            }

            vbuf.AddVertexArray (positionName, posArray);
            if (normArray != null) {
                vbuf.AddVertexArray (normalName, normArray);
            }

            for (p = 0; p < primitives.Length; p++) {
                ibuf.Set (p, 1, primitives[p]);
            }

            var msh = new Mesh ();
            msh.SetVertexBuffer (vbuf);
            msh.SetIndexBuffer (ibuf);


            return msh;
        }
    }
}
